<!DOCTYPE html>
<html lang="en" dir="auto">

<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="index, follow">
<title>From Transformer to VGGT | WangJV Blog</title>
<meta name="keywords" content="Transformer, ViT, VGGT, 三维重建, 计算机视觉">
<meta name="description" content="1. Preliminary: Attention and ViT
我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。

1.1. Encoder and Decoder
深度学习名著 《Attention is all you need》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。
Encoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add&amp;Norm）那个块。因此，每个子层的输出可以表示为：
$$
\text{LayerNorm}(x&#43;\text{Sublayer}(x))
$$其中 $\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。
Decoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。">
<meta name="author" content="WangJV">
<link rel="canonical" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/">
<link crossorigin="anonymous" href="https://wangjv0812.github.io/WangJV-Blog-Pages/assets/css/stylesheet.8fe10233a706bc87f2e08b3cf97b8bd4c0a80f10675a143675d59212121037c0.css" integrity="sha256-j&#43;ECM6cGvIfy4Is8&#43;XuL1MCoDxBnWhQ2ddWSEhIQN8A=" rel="preload stylesheet" as="style">
<link rel="icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico">
<link rel="icon" type="image/png" sizes="16x16" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="https://wangjv0812.github.io/WangJV-Blog-Pages/favicon-32x32.png">
<link rel="apple-touch-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/apple-touch-icon.png">
<link rel="mask-icon" href="https://wangjv0812.github.io/WangJV-Blog-Pages/safari-pinned-tab.svg">
<meta name="theme-color" content="#2e2e33">
<meta name="msapplication-TileColor" content="#2e2e33">
<link rel="alternate" hreflang="en" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/">
<noscript>
    <style>
        #theme-toggle,
        .top-link {
            display: none;
        }

    </style>
    <style>
        @media (prefers-color-scheme: dark) {
            :root {
                --theme: rgb(29, 30, 32);
                --entry: rgb(46, 46, 51);
                --primary: rgb(218, 218, 219);
                --secondary: rgb(155, 156, 157);
                --tertiary: rgb(65, 66, 68);
                --content: rgb(196, 196, 197);
                --code-block-bg: rgb(46, 46, 51);
                --code-bg: rgb(55, 56, 62);
                --border: rgb(51, 51, 51);
            }

            .list {
                background: var(--theme);
            }

            .list:not(.dark)::-webkit-scrollbar-track {
                background: 0 0;
            }

            .list:not(.dark)::-webkit-scrollbar-thumb {
                border-color: var(--theme);
            }
        }

    </style>
</noscript><script>
  MathJax = {
    tex: {
      displayMath: [['\\[', '\\]'], ['$$', '$$']],
      inlineMath: [['\\(', '\\)'], ['$', '$']],
      processEscapes: true,
      processEnvironments: true,
      tags: 'ams'
    },
    chtml: {
      scale: 1,                     
      minScale: 0.5,               
      matchFontHeight: false,       
      displayAlign: 'center',       
      displayIndent: '0',           
      mtextInheritFont: false,      
      merrorInheritFont: true,      
      mathmlSpacing: false,         
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    svg: {
      scale: 1,                     
      minScale: 0.5,               
      mtextInheritFont: false,      
      merrorInheritFont: true,
      mathmlSpacing: false,
      skipHtmlTags: ['script','noscript','style','textarea','pre','code','a'],
      ignoreHtmlClass: 'tex2jax_ignore',
      processHtmlClass: 'tex2jax_process'
    },
    options: {
      enableMenu: true,             
      menuOptions: {
        settings: {
          zoom: 'Click'             
        }
      }
    },
    loader: {
      load: ['ui/safe', 'a11y/assistive-mml']
    },
    startup: {
      ready() {
        MathJax.startup.defaultReady();
        
        const observer = new ResizeObserver(entries => {
          MathJax.typesetPromise();
        });
        observer.observe(document.body);
      }
    }
  };
  
  
  if (window.innerWidth <= 768) {
    MathJax.chtml = MathJax.chtml || {};
    MathJax.chtml.scale = 0.9;  
  }
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>


<style>
   
  .MathJax {
    outline: 0;
  }
  
   
  @media (max-width: 768px) {
    .MathJax {
      font-size: 90% !important;
    }
    
     
    .MathJax_Display {
      overflow-x: auto;
      overflow-y: hidden;
      padding: 0 !important;
      margin: 1em 0 !important;
    }
    
     
    .MathJax_CHTML {
      line-height: 1.2 !important;
    }
  }
  
   
  mjx-container[jax="CHTML"][display="true"] {
    overflow-x: auto;
    overflow-y: hidden;
    padding: 1px 0;
  }
</style><meta property="og:url" content="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/">
  <meta property="og:site_name" content="WangJV Blog">
  <meta property="og:title" content="From Transformer to VGGT">
  <meta property="og:description" content="1. Preliminary: Attention and ViT 我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。
1.1. Encoder and Decoder 深度学习名著 《Attention is all you need》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。
Encoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add&amp;Norm）那个块。因此，每个子层的输出可以表示为：
$$ \text{LayerNorm}(x&#43;\text{Sublayer}(x)) $$其中 $\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。
Decoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。">
  <meta property="og:locale" content="en-us">
  <meta property="og:type" content="article">
    <meta property="article:section" content="posts">
    <meta property="article:published_time" content="2025-04-18T16:40:25+08:00">
    <meta property="article:modified_time" content="2025-04-18T16:40:25+08:00">
    <meta property="article:tag" content="Transformer">
    <meta property="article:tag" content="ViT">
    <meta property="article:tag" content="VGGT">
    <meta property="article:tag" content="三维重建">
    <meta property="article:tag" content="计算机视觉">
      <meta property="og:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://wangjv0812.github.io/WangJV-Blog-Pages/">
<meta name="twitter:title" content="From Transformer to VGGT">
<meta name="twitter:description" content="1. Preliminary: Attention and ViT
我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。

1.1. Encoder and Decoder
深度学习名著 《Attention is all you need》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。
Encoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add&amp;Norm）那个块。因此，每个子层的输出可以表示为：
$$
\text{LayerNorm}(x&#43;\text{Sublayer}(x))
$$其中 $\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。
Decoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。">


<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position":  1 ,
      "name": "Posts",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/posts/"
    }, 
    {
      "@type": "ListItem",
      "position":  2 ,
      "name": "From Transformer to VGGT",
      "item": "https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/"
    }
  ]
}
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "From Transformer to VGGT",
  "name": "From Transformer to VGGT",
  "description": "1. Preliminary: Attention and ViT 我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。\n1.1. Encoder and Decoder 深度学习名著 《Attention is all you need》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。\nEncoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add\u0026amp;Norm）那个块。因此，每个子层的输出可以表示为：\n$$ \\text{LayerNorm}(x+\\text{Sublayer}(x)) $$其中 $\\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。\nDecoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。\n",
  "keywords": [
    "Transformer", "ViT", "VGGT", "三维重建", "计算机视觉"
  ],
  "articleBody": "1. Preliminary: Attention and ViT 我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。\n1.1. Encoder and Decoder 深度学习名著 《Attention is all you need》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。\nEncoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add\u0026Norm）那个块。因此，每个子层的输出可以表示为：\n$$ \\text{LayerNorm}(x+\\text{Sublayer}(x)) $$其中 $\\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。\nDecoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。\n1.2. Attention 注意力可以被看作一个函数，将 Querry、Key、Value 映射到一个输出上。Querry、Key、Value 和输出都是向量。事实上，输出可以看作一个对 Value 的加权输出。而具体如何用 Querry 和 Key 来加权 Value，就是注意力机制了，事实上这个权描述了对应的 Value 在这个任务上的重要程度。具体而言，此处使用的是 Scaled Dot-Product Attention。\n上图左侧就是 Scaled Dot-Product Attention。我们假设输入 querry 和 key 的维度是 $d_k$，Value 的维度是 $d_v$，我们计算 querry 和所有 key 的点积并数乘以 $\\frac{1}{\\sqrt{d_k}}$，之后使用一个 softmax 来得到 value 的权重。事实上我们一堆 querry，key，value （batchsize）打包成矩阵 $Q, K, V$ 来加速计算。最终，矩阵形式的 Attention 可以写作：\n$$ \\text{Attention}(Q, K, V) = \\text{softmax}\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V $$事实上，Scaled Dot-Product Attention 并不是唯一的注意力即使（加权方法）。但是更多的 Attention mechanism，例如 点积注意力（dot-product attention）、$QKV$ 注意力（query-key-value attention）、强注意力（hard attention）、软注意力（soft attention）、自注意力（self attention）、交叉注意力（cross attention）、Luong 注意力、Bahdanau 注意力等。我们就不赘述。\n1.3. Multi-head Attention 相比一个单头注意力机制直接解决，Multi-head Attention 机制将注意力机制分成了 $h$ 个单独的头，每个头通过三个对应的科学系的先行变换层将输入的 $QKV$ 变换到这个注意力头专用的 $QKV$ 空间中，分别投影到 $d_k, d_k, d_v$ 维度上。每个头上都进行经典的 Scaled Dot-Product Attention 计算。之后将这些头的输出结果（对于每个头而言，单个querry 而言，输出结果是一个标量）堆叠起来组成一个向量（对于 batch 的角度而言就是矩阵了），再通过一个可学习的线形映射层综合这些信息。\n这些输出值会被拼接起来，然后再次进行投影，从而得到最终的值，如图2所示。\n多头注意力机制使模型能够在不同位置同时关注来自不同表示子空间的信息。如果仅使用单个注意力头，这种信息的综合利用就会受到限制。\n$$ \\begin{aligned} \\text{MultiHead}(Q, K, V) \u0026= \\text{Concat}(\\text{head}_1, ..., \\text{head}_h)W^O\\\\ \\text{where: } \\text{head}_i \u0026= \\text{Attention}(QW^Q_i, KW^K_i, VW^V_i) \\end{aligned} $$这里的投影矩阵分别为：$W^Q_i \\in R^{d_{model} \\times d_k}$，$W^K_i \\in R^{d_{model} \\times d_k}$，$W^V_i \\in R^{d_{model} \\times d_v}$，以及 $W^O \\in R^{hd_v \\times d_{model}}$。\n1.4. Attention 的一些使用细节 需要注意的一点是，为了保证残差链接，encoder 中每一层的输出均为和输入相同维度的向量。因此事实上这张 pipline 是有些误导性的。其 $QKV$ 均为前一层的输出，事实上是通过多头注意力机制中的线形变换矩阵 $W^Q_i, W^K_i, W^V_i$ 变换到对应的 $QKV$ 的维度的。一般而言，我们称之为自注意力机制，其 $QKV$ 都是来源于同一个地方的 在 decoder 中的注意力处理与 encoder 有所不同，其 querry 来源于上一个 decoder 层的输出，keys 和 values 来源于 encoder 的输出。别忘了 Transformer 提出时是为了解决翻译问题，这样设计的好处是在自回归预测下一个词时，可以关注被翻译的句子的全部信息，同时为了保证句子的连贯性，decoder 中的注意力时 masked 的，即只能看到已经计算完成的信息。 同样地，解码器中的自注意力层允许解码器中的每个位置关注解码器中直至并包含该位置的所有位置。为了保持自回归特性，我们需要阻止解码器中的信息向左流动。我们通过在缩放点积注意力机制中，将 softmax 输入里所有对应于非法连接的值屏蔽掉（设为负无穷）来实现这一点。 1.5. 逐位置（position-wise）的前馈网络 在每个 encoder 和 decoder 层中，除了注意力机制之外，还附加了一个全连接前馈网络。这个前馈网络会对输入的每个词操作。事实上这是一个非常简单的网络，只有两层，两层之间包含一个Relu。可以写作：\n$$ \\text{FNN}(x) = max(0, xW_1 + b_1)W_2 + b_2 $$尽管不同位置上的线性变换是相同的，但不同层之间使用的参数是不同的。我们可以将其视为两次核大小为 $1$ 的卷积操作。输入和输出的维度为 $d_{model}=512$，而中间层的维度为 $d_{ff}=2048$。\n需要简单说明的是，这里的逐位置指的就是针对每个位置计算的 Value，都有一个对应的前馈网络。\n1.6. 位置编码 因为 Transformer 中不包含任何循环或者卷积结构，任意顺序的输入对于 Transformer 而言都是平等的。其网络结构天然的不擅长捕捉序列间的相对位置关系。事实上也就因如此，Transformer 对并行化训练十分友好（以为各个位置的输入都是平等的，或者说相同的，先算谁后算谁都是一个道理）。我们必须给序列中引入一些标记，让其体现出输入的相对或绝对位置信息。为此，我们在编码器和解码器堆叠层底部的输入嵌入中添加了“位置编码”。位置编码与嵌入的维度相同，均为 $d_{model}$，这样二者就可以直接相加。\n在这项工作中，我们使用不同频率的正弦和余弦函数：\n$$ PE_{(pos, 2i)} = \\sin\\left(\\frac{pos}{10000^{2i / d_{model}}}\\right)\\\\ PE_{(pos, 2i + 1)} = \\cos\\left(\\frac{pos}{10000^{2i / d_{model}}}\\right) $$其中 $pos$ 表示位置，$i$ 表示维度。也就是说，位置编码的每个维度都对应一个正弦曲线。波长形成一个从 $2\\pi$ 到 $10000\\cdot 2\\pi$ 的等比数列。我们选择这个函数，是因为我们推测它能让模型轻松地通过相对位置来学习注意力机制，这是因为对于任何固定的偏移量 $k$，$PE_{pos + k}$ 都可以表示为 $PE_{pos}$ 的线性函数。\n1.7. 复杂度分析 Layer Type Complexity per Layer Sequential Operations Maximum Path Length Self - Attention $O(n^2 \\cdot d)$ $O(1)$ $O(1)$ Recurrent $O(n \\cdot d^2)$ $O(n)$ $O(n)$ Convolutional $O(k \\cdot n \\cdot d^2)$ $O(1)$ $O(\\log_k(n))$ Self - Attention (restricted) $O(r \\cdot n \\cdot d)$ $O(1)$ $O(\\frac{n}{r})$ 事实上，Transformer 的改进可太多了，例如应对巨量文本输入的稀疏注意力、对注意力计算结果缓存（KV Cache、Flash Attention）、针对位置编码的改进（RoPE）等等等等。要全部讨论这些可能需要不止一次 Seminar 我们后面找机会再讨论吧。\n1.8. Vision Transformer ViT将输入图片分为多个patch（16x16），再将每个patch投影为固定长度的向量送入Transformer，后续encoder的操作和原始Transformer中完全相同。但是因为对图片分类，因此在输入序列中加入一个特殊的token，该token对应的输出即为最后的类别预测。按照上面的流程，ViT 可以分为下面几个步骤：\nPath embedding：例如输入照片的尺寸为 $244 \\times 244$，之后将图片分为大小为 $16 \\times 16$ 的 Patch。因此每个图片会产生 $\\frac{244}{16} \\times \\frac{244}{16} = 196$ patch。按照传统 Transformer 的话来说，就是输入序列的长度为 196。每个 patch 或者说 embedding 的维度为 $16\\times 16\\times 3 = 768$，线形投影层的维度为 $768 \\times N$。那么通过线形投影层变换后的维度为 $196\\times N$。即 $196$ 个 token，每个 token 的维度为 $N$。但是还没完，最终还需要添加一个特殊的token cls 标记序列结束了。所以此时 attention mecanism 输入的维度为 $197\\times N$。不难发现，此时已将一个图像处理问题转换为一个 seq2seq 问题。 Transformer 的对序列相对/绝对关系的缺点（或者说是优点）依然存在，我们需要给每个图片的 patch 编码。此处的方法和 Transformer 完全相同。对于每个 patch 生成一个只与其位置有关，维度为 $N$ 的编码，直接加到 patch embedding 上。这样就能保留 patch 的位置信息了。 multihead attention。对于多个头的情况（例如 12 个头），ViT 会通过一个线形映射将输入的 embedding 降维映射到维度为 $N // 12$ 的 QKV 上，每个头处理后再将输出拼接起来，还原为原始的维度 $N$。之后通过一个先变大后变小的 MLP。最终每层输出的维度依然为 $197\\times N$ $$ \\begin{array}{ll} z_{0}=\\left[x_{class } ; x_{p}^{1} E ; x_{p}^{2} E ; \\cdots ; x_{p}^{N} E\\right]+E_{pos}, \u0026E \\in \\mathbb{R}^{(P^{2} \\cdot C) \\times D}, E_{pos} \\in \\mathbb{R}^{(N + 1) \\times D}\\\\ z_{\\ell}'=\\text{MSA}(\\text{LN}(z_{\\ell - 1})) + z_{\\ell - 1}, \u0026\\quad \\ell = 1 \\cdots L\\\\ z_{\\ell}=\\text{MLP}(\\text{LN}(z_{\\ell}')) + z_{\\ell}', \u0026\\quad \\ell = 1 \\cdots L\\\\ y=\\text{LN}(z_{L}^{0}) \\end{array} $$读到这里你可能会想，为什么 ViT 这样一个显然的思路之前没人做呢？事实上当网络的规模不够大时，ViT 的效果是不不过 ResNet 或者 CNN 的。但是当训练数据集不够大的时候，ViT的表现通常比同等大小的ResNets要差一些，因为Transformer和CNN相比缺少归纳偏置（inductive bias），即一种先验知识，提前做好的假设。\nCNN具有两种归纳偏置：\n一种是局部性（locality/two-dimensional neighborhood structure），即图片上相邻的区域具有相似的特征 一种是平移不变性（translation equivariance），$f(g(x)) = g(f(x))$ ，其中 $g$ 代表卷积操作，$f$ 代表平移操作。 当CNN具有以上两种归纳偏置，就有了很多先验信息，需要相对少的数据就可以学习一个比较好的模型。而当数据量和网络规模变大时，Transformer 的优势才体现出来。ViT 则相信 Transformer 的表达能力，即使没有归纳偏置，也能从数据中学习到足够的先验知识。ViT 通过大规模数据集的训练，获得了比 CNN 更好的性能。\n2. VGGT Introduction VGGT 是一个大型的前馈网络，通过输入一张照片到多张照片（甚至几百张）作为输入；直接推理出一个场景几乎所有的三维特征，包括相机矩阵、点图（point map）、深度图和三维点跟踪（3D Point Tracks）。传统的三维重建方法通常基于视觉几何，使用例如 BA 之类迭代优化的方法。机器学习常常扮演着重要的补充角色，用于处理仅靠几何方法无法解决的任务，比如特征匹配和单目出深度。二者的融合越来越紧密，如今像 VGGSfM 这样 SOTA 的 SfM 方法，通过可微光束法平差将机器学习和视觉几何进行端到端的结合。即便如此，视觉几何在三维重建中仍起着主要作用，这增加了问题的复杂性和计算成本。\n本文作者发现，没有必要为三维重建任务设计一个专用的网络结构，VGGT 直接使用了标注你的大型 Transformer，没有使用特殊的三维或其他归纳偏置，而是用一种通用的网络结构在极大量的有三维标注的公开图像数据集上训练。和其他针对三维任务的大型神经网络，例如 DepthAnything、MoGe、LRM 等只针对一个特定的三维任务（例如单目深度估计）。VGGT 使用一个共享的骨干网络来预测所有感兴趣的三维特征，在推理过程中可以从预测出的深度图和相机矩阵计算出 pointmap，与直接使用专门的 pointmap 预测头相比，能获得更高的精度。\n总而言之，这篇文章的贡献如下\n引入了视觉几何基础变换器（VGGT），这是一个大型的前馈 Transformer。给定一个场景的单张、几张甚至数百张图像，它能够在数秒内预测出该场景的所有关键三维属性，包括相机的内参和外参、点云图、深度图以及三维点轨迹。 VGGT 的预测结果可直接使用，其竞争力很强，并且通常优于那些使用缓慢的后处理优化技术的最先进方法的预测结果。 当进一步结合光束法平差（BA）后处理时，即使与那些专门针对某一类三维任务的方法相比，VGGT 也全面取得了领先的成果，并且常常能大幅提升任务处理的质量。 3. Method 3.1. 问题定义和符号约定 对于一个输入网络侧图片序列 $(I_i)^N_{i = 1}$，表示 $N$ 张 RGB 图片 $I_i \\in \\mathbb R^{3\\times H \\times W}$，我们默认这些图片是对同一个场景的观测。VGGT 使用的 Transformer 认为也是一个映射，将这个图片序列映射到这个三维场景的描述，对于每一帧，这个映射可以写作：\n$$ f\\left(\\left(I_{i}\\right)_{i = 1}^{N}\\right)=\\left(g_{i}, D_{i}, P_{i}, T_{i}\\right)_{i = 1}^{N} $$这个 Transformer 将每张图像 $I_i$ 映射为其相机参数 $g_i \\in \\mathbb{R}^9$，包含内参和外参；深度图 $D_i \\in \\mathbb{R}^{H×W}$；其点图 $P_i \\in \\mathbb{R}^{3×H×W}$；以及一个用于点匹配的的 $C$ 维特征 $T_i \\in \\mathbb{R}^{C×H×W}$。接下来我们将解释这些量是如何定义的。\n相机参数 $g_i$ 定义为：$g = [\\bf q, \\bf t, \\bf f]$，是将描述旋转的四元数 $\\bf q \\in \\mathbb R^4$，描述平移的向量 $\\bf t \\in \\mathbb R^3$ 和焦距 $\\bf f \\in \\mathbb R^2$ 组合在一起的。此处我们假设了相机为针孔模型。\n我们定义图片 $I_i$ 的定义域为 $\\mathcal I(I_i) = \\{1, 2, \\dots, H\\}\\times \\{1, 2, \\dots, W\\}$，是所有可能的像素的集合。深度图 $D_{i}$ 将每个像素位置 $y \\in I(I_{i})$ 与其对应的深度值 $D_{i}(y) \\in \\mathbb{R}^{+}$ 相关联，该深度值是从第 $i$ 个相机观测到的。类似的，pointmap $P_{i}$ 将每个像素与其对应的三维场景点 $P_{i}(y) \\in \\mathbb{R}^{3}$ 相关联。与 DUSt3R 一致，三维点 $P_{i}(y)$ 的坐标系为第一个相机 $g_{1}$ 的坐标系中。换句话说，我们认为第一个相机 $g_{1}$ 的坐标系为世界坐标系\n最后，关于关键点跟踪（point tracking）。对于图像 $I_q$ 中的一个固定点 $y_q$，网络将输出一个跟踪 $\\mathcal T^\\star (y_q) = (y_i)^N_{i = 1}$，即其他所有图片上的对应点。需要注意的是，网络并不会直接输出轨迹，而是输出用于跟踪的特征 $T_i \\in \\mathbb{R}^{C \\times H \\times W}$。具体的点的跟踪将会由一个独立的网络实现：\n$$ T \\left( (y_j)_{j=1}^M, (T_i)_{i=1}^N \\right) = \\left( (\\hat{y}_{j,i})_{i=1}^N \\right)_{j=1}^M $$这个网络 $\\mathcal T$ 输入跟踪点 $y_q$ 和稠密的跟踪 feather $T_i$，计算对应的其他照片中的对应点。跟踪网络 $\\mathcal T$ 和主网络 $f$ 是一起端到端训练的。\n在推理和训练时，除了第一张照片的输入需要特别注意意外，其他照片可以以任意顺序输入。因为第一张照片将作为参考坐标系。VGGT 的网络结构在设计时特意考虑了这一点，让除了第一张照片以外的任意输入都是等价的。在后面网络结构的介绍中我们将看到这一点。\n事实上，VGGT 的网络具有 “over-complete prediction” 的性质。换句话说，它的有些预测是相关的，例如预测的相机参数 $\\bf g$ 可以从不变的 pointmap $P$ 计算出。此外，深度图还可以从 pointmap 和相机矩阵推出。事实上正是这种预测结果之间的闭式关系让我们实现预测量之间的相互监督，实现了显著的心梗提升。事实上与直接专门预测 pointmap 的网络相比，结合相机参数和深度图的相互监督可以提升 pointmap 的预测精度。\n3.2. 特征提取骨干网络 事实上，VGGT 通过一个巨大的 Transformer （1.2B）实现了映射 $f$ 的功能：\n$$ f\\left(\\left(I_{i}\\right)_{i = 1}^{N}\\right)=\\left(g_{i}, D_{i}, P_{i}, T_{i}\\right)_{i = 1}^{N} $$首先，VGGT 先通过 DINO 将图片 $I$ 分割为一组 $K$ 个 token $t^I \\in \\mathbb R^{K\\times C}$。之后，所有帧的 token 组的并 $t^I = \\cup^N_{i = 1}\\{t_i^I\\}$ 通过骨干网络来处理，进行全局自注意力。\n3.3. 交替注意力机制 VGGT 引入了交替注意力机制对经典的 Transformer 的注意力机制做了调整，使 Transformer 能够以交替的方式聚焦于每个帧内和全局的信息。具体而言，帧级的注意力机制单独处理每个帧内的 token $t_k^I$，而全局注意力则将所有帧的 token 拼接在一起 $t_I$ 处理。这种设计在整合不同图像间信息与归一化每个图像内令牌的激活值之间取得了平衡。本文中使用 $L=24$ 层的全局-局部注意力机制。\n具体的 Alternative Attention Transformer 的机制论文中没有说的非常明白，我们不妨回到论文，看看具体是怎么做的。首先是 global attention 和 frame attention 的定义，我们此处不贴具体代码了，只说结论，global attention 和 frame attention 的 block 是完全相同的。在具体处理时：\nself.aa_order=[\"frame\", \"global\"] ······· for _ in range(self.aa_block_num): for attn_type in self.aa_order: if attn_type == \"frame\": tokens, frame_idx, frame_intermediates = self._process_frame_attention( tokens, B, S, P, C, frame_idx, pos=pos ) elif attn_type == \"global\": tokens, global_idx, global_intermediates = self._process_global_attention( tokens, B, S, P, C, global_idx, pos=pos ) else: raise ValueError(f\"Unknown attention type: {attn_type}\") for i in range(len(frame_intermediates)): # concat frame and global intermediates, [B x S x P x 2C] concat_inter = torch.cat([frame_intermediates[i], global_intermediates[i]], dim=-1) output_list.append(concat_inter) 其中 B 表示 batch size，即批量大小，S 表示 sequence length，即图片序列长度。P 表示每张照片的 patch 数量，C 表示每个 patch 的维度。具体的 frame attention 和 global attention 的实现细节如下：\ndef _process_frame_attention(self, tokens, B, S, P, C, frame_idx, pos=None): \"\"\" Process frame attention blocks. We keep tokens in shape (B*S, P, C). \"\"\" # If needed, reshape tokens or positions: if tokens.shape != (B * S, P, C): tokens = tokens.view(B, S, P, C).view(B * S, P, C) if pos is not None and pos.shape != (B * S, P, 2): pos = pos.view(B, S, P, 2).view(B * S, P, 2) intermediates = [] # by default, self.aa_block_size=1, which processes one block at a time for _ in range(self.aa_block_size): tokens = self.frame_blocks[frame_idx](tokens, pos=pos) frame_idx += 1 intermediates.append(tokens.view(B, S, P, C)) return tokens, frame_idx, intermediates def _process_global_attention(self, tokens, B, S, P, C, global_idx, pos=None): \"\"\" Process global attention blocks. We keep tokens in shape (B, S*P, C). \"\"\" if tokens.shape != (B, S * P, C): tokens = tokens.view(B, S, P, C).view(B, S * P, C) if pos is not None and pos.shape != (B, S * P, 2): pos = pos.view(B, S, P, 2).view(B, S * P, 2) ········ 和上一个函数完全相同，就不赘述了 return tokens, global_idx, intermediates 这样，我们可以缕出 alternative attention 的完整逻辑。实际上不论是 frame attention 还是 global attention，都是一个完整的 transformer block。对于 VGGT 的每一层，输入的 token 先通过 frame attention 处理，之后再通过 global attention 处理。（此处和论文有所不同，论文 pipline 是先经过 global attention 后 frame attention），以此往复 24 次。\n事实上 global attention 和 frame attention的实现也十分简单。global attention 实际上是将所有的帧的 tokne 堆叠在一起处理做自注意力，而 frame attention 则是将帧当作 batch 处理，也就是代码中的：\ntokens = tokens.view(B, S, P, C).view(B * S, P, C) tokens = tokens.view(B, S, P, C).view(B, S * P, C) 3.4. 预测头 下面我们要讨论 VGGT 是如何通过 Transformer 和预测头实现这神乎其神的功能。\n首先，VGGT 在每张图片用 DINO pachify 的 token $t_i^I$ 的基础上，给每个图片都添加了一个额外的相机 token $t_i^g\\in \\mathbb R^{1\\times C^\\prime}$ 和 4 个寄存器 token $t_i^R\\in \\mathbb R^{4\\times C^\\prime}$，由此，每个图片的 token 可以表达为：$\\{t_i^g, t_i^R, t_i^I\\}$。这些添加的 5个 token 是网络的可学习参数。\n这里原论文没有说明白的一点是，事实上，我们只添加了两组 camera token 和 register token。第一组 camera token 是第一帧专项的，记做：\n$$ \\begin{array}{l} t_1^g = \\overline{t}^g\u0026 t_1^R = \\overline{t}^R \\end{array} $$其他所有帧公用另外一组 camera token 和 register token，记做：\n$$ \\begin{array}{cc} \\begin{aligned} t_i^g \u0026= \\overline{\\overline{t}}^g\\\\ t_i^R \u0026= \\overline{\\overline{t}}^R \\end{aligned} \u0026\\forall i \\in \\{2,3,\\dots,N\\} \\\\ \\end{array} $$camera token 和 register token 经过网络预测记做：\n$$ \\begin{aligned} f(t_i^g) \u0026= \\hat{t_i^g}\\\\ f(t_i^R) \u0026= \\hat{t_i^R}\\\\ f(t_i^I) \u0026= \\hat{t_i^I}\\\\ \\end{aligned} $$在预测相机参数时，我们只使 $\\hat{t_i^g}$，register token 被抛弃了。\n3.4.1 坐标系 如前所述，我们在第一台相机 $g_{1}$ 的坐标系中预测相机参数、点图和深度图。因此，第一台相机的输出相机外参被设置为单位矩阵，即第一个旋转四元数为 $q_{1}=[0,0,0,1]$，第一个平移向量为 $t_{1}=[0,0,0]$。回顾可知，特殊的相机令牌和寄存器令牌 $t_{1}^{g}:=\\overline{t}^{g}$、$t_{1}^{R}:=\\overline{t}^{R}$ 使 Transformer 能够识别第一台相机。\n3.4.2 相机参数预测头 VGGT 通过刚才添加的相机 token 预测出的 token $(\\hat t_u^g)_{i=1}^N$ 来预测相机参数 $(\\hat g^i)_{i=1}^N$。$(\\hat t_u^g)_{i=1}^N$ 通过连接 4 个额外的连接了线形层的自注意力层进行预测，这构成了相机内参的预测头。\n事实上使用的网络比论文描述的稍微复杂不少。相机参数预测头会多轮优化预测的结果。大致的 pipline 可以被描述为：\n当一个新的迭代开始时，先会使用一个全为 0 的位置编码，通过一个线形映射投影到 token 的维度 如果这不是第一轮，之前已经有了迭代结构，则会用上一轮的优化结果映射到 token 的维度（当然这里需要detach，避免梯度回传），叫做 module_input。 之后会通过一个 SiLU 网络和一个很浅的全连接调整维度，将 module_input映射到 shift, scale和 gate。 之后用 shift，scale 对归一化的 $(\\hat g^i)_{i=1}^N$ 做仿射变换，用于体现之前优化的结构 $$ \\text{pose\\_tokens\\_modulated} = (1 + scale) * (\\hat g^i)_{i=1}^N + shift $$ 对 pose_tokens_modulated 和 $(\\hat g^i)_{i=1}^N$ 残差连接，之后进行 transformer 最后进行全连接调整维度和预测 最终将调整的结果加在上一轮迭代结果上 事实上，这样做是因为 Transformer 更擅长与处理上下文语意，而相机参数恰好是一个尺度敏感的参数。因此此处用了一个 学习的放射变换来调整。\n3.4.3 稠密预测头 输出的图像 token $\\hat t^I_i$ 用于预测稠密输出的深度图 $D_i$、点图 $P_i$ 和用于跟踪的特征 $T_i$。更具体的说，VGGT 首先将 $\\hat t_i^I$ 通过 DPT 层转换为较为稠密的特征图 $F_i \\in \\mathbb R^{C^{\\prime \\prime} \\times H\\times W}$，之后 $F_i$ 通过一个 $3 \\times 3$ 的卷积层映射到对应的深度图 $D_i$ 和 $P_i$。此外，DPT 还输出了稠密预测特征 $T_i \\in \\mathbb R^{C\\times H \\times W}$，将输入后面的跟踪头。\n此外，VGGT 还预测出了每个深度图和 pointmap 的不确定性图 $\\Sigma_i^D, \\Sigma_i^P \\in \\mathbb R_+^{H\\times W}$。这个不确定性图和 MASt3R 相同，用于加权的计算损失。\n3.4.4 跟踪头 跟踪网络 $\\mathcal T$ 采用了 CoTracker2 架构。对于一个等待 track 的三维点集 $y_j$ 和这个点对应的图片提取出的特征 $T_i$，跟踪网络 $\\mathcal T$ 将会计算出所有的照片中 $y_j$ 对应的二维点。\n$$ T\\big((y_j)_{j = 1}^M, (T_i)_{i = 1}^N\\big)=((\\hat{y}_{j,i})_{i = 1}^N)_{j = 1}^M $$通过上面的形式可以看出相对于三维点而言，骨干网络提取出的特征 $T_i$ 可能是不对齐的，因此首先我们要在每个查询点 $y_j$ 上对 $T_i$ 双线形差值。然后将该特征与其他特征图 $T_i$ 进行关联，得到一组关联图。接着通过自注意力机制对关联图处理，以预测出最终对应的二维点 $\\hat y_i$。\n4. Training 训练时，VGGT 使用了如下的 lossfunction：\n$$ \\mathcal L = \\mathcal L_{\\text{camera}} + \\mathcal L_{\\text{pmap}} + \\lambda\\mathcal L_{\\text{track}} $$有趣的是，VGGT 训练时发现 $\\mathcal L_{\\text{camera}}$ 和 $\\mathcal L_{\\text{pmap}}$ 的尺度是相似的，因此不需要通过缩放因子来调整。但是 $\\mathcal L_{\\text{track}}$ 不同，需要调整。一般而言有 $\\lambda = 0.05$。\n4.1. 相机损失 相机损失 $\\mathcal L_{\\text{camera}}$ 用于监督相机参数 $\\hat{\\bf g}$:\n$$ \\mathcal L_{\\text{camera}} = \\sum_{i=1}^N \\|\\hat{\\bf g}_i - \\bf g_i \\|_\\epsilon $$其中 $\\| \\cdot \\|_\\epsilon$ 是 huber 损失。\n4.2. 深度和点图损失 深度损失 $\\mathcal L_{\\text{depth}}$ 和 DUSt3R 相同，使用不确定性图加权了预测深度图 $\\hat D_i$ 与 真值 $D_i$ 之间的残差。与 DUSt3R 不同的是，VGGT 还引入了单目深度估计中的图像梯度项，损失可以被写作：\n$$ L_{\\text{depth}} = \\sum_{i=1}^{N} \\|\\Sigma_i^D \\odot (\\hat{D}_i - D_i)\\| + \\|\\Sigma_i^D \\odot (\\nabla \\hat{D}_i - \\nabla D_i)\\| - \\alpha \\log \\Sigma_i^D $$其中 $\\odot$ 表示通道广播逐元素乘积（Channel-broadcast element-wise product）（看着这个名字很吓人，实际上就是数乘，因为 $\\Sigma_i^D\\in \\mathbb R^{H\\times W}$，而 $\\nabla \\hat{D}_i\\in \\mathbb R^{H\\times W\\times 2}，需要乘到向量上$ ）。$\\alpha$ 是一个超参数。\npoint map 的损失和深度损失很类似，可以写作：\n$$ L_{\\text{pmap}} = \\sum_{i=1}^{N} \\|\\Sigma_i^P \\odot (\\hat{P}_i - P_i)\\| + \\|\\Sigma_i^P \\odot (\\nabla \\hat{P}_i - \\nabla P_i)\\| - \\alpha \\log \\Sigma_i^P $$4.3. 跟踪损失 跟踪损失可以写作：\n$$ L_{\\text{track}} = \\sum_{j=1}^{M} \\sum_{i=1}^{N} \\|y_{j,i} - \\hat{y}_{j,i}\\| $$其中外层求和是遍历查询图像 $I_q$ 所有的查询点 $y_j$，内层循环遍历所有可能的照片。$y_{j,i}$ 为在图像 $i$ 中的真实点。$\\hat{y}_{j,i}$ 为跟踪模块的预测结果 $T((y_j)_{j=1}^{M}, (T_i)_{i=1}^{N})$。\n4.4. 真值坐标系归一化 如果我们缩放一个场景或者改变其参考系，这个场景的照片事实上不会有任何变换。这意味着改变参考系和缩放在三维重建上是正常的，或者说等价的，换句话说就是三维重建的尺度不一致性。这种不同图片预测出的结果的尺度不一致性是无法自然的通过损失来监督的，因此要对真值做归一化。这个归一化事实上分为两步：\n将所有照片的坐标系转换到第一个照片的坐标系中 计算该场景中所有三维点在异地个照片的坐标系中，距离坐标原点的平均欧几里得距离。用这个平均距离来归一化相机外参 $t$ 深度图 $D$ 和点图 $P$。 一个很有趣的细节，或者说和 M/DUSt3R 的不同之处是，VGGT 不会对预测结果归一化，而是迫使模型从数据集学习这种归一化的方式\n4.5. 训练细节 实现细节。默认情况下，我们分别使用 $L = 24$ 层的全局注意力和帧级注意力。该模型总共约有 1.2B 参数。我们通过 AdamW 优化器对训练损失进行 16 万轮优化来训练模型。我们使用余弦学习率调度器，峰值学习率为 0.0002，预热 8000 轮。对于每个批次，我们从随机选取的训练场景中随机采样 2 - 24 帧。输入的帧、深度图和点图被调整为最大尺寸为 518 像素。宽高比在 0.33 到 1.0 之间随机设定。我们还对帧随机应用颜色抖动、高斯模糊和灰度增强等操作。训练在 64 块 A100 GPU 上进行，持续九天。我们采用梯度范数裁剪，阈值设为 1.0，以确保训练的稳定性。我们利用 bfloat16 精度和梯度检查点技术来提高 GPU 内存和计算效率。\n5. Experiments References Attention is all you need\nViT（Vision Transformer）解析\nAN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE\nwiki vision transformer\nVGGT: Visual Geometry Grounded Transformer\nVGGT opensource coed in github\nVision Transformer for Dense Prediction\nAppendix A. huber loss $$ L_\\delta(a) = \\begin{cases} \\frac{1}{2} a^2, \u0026 \\text{if } |a| \\leq \\delta \\\\ \\delta(|a| - \\frac{1}{2} \\delta), \u0026 \\text{if } |a| \u003e \\delta \\end{cases} $$其中：\n$a = y - \\hat{y}$ 表示预测值和真实值的差； $\\delta$ 是一个超参数，控制“哪些差值被当作离群点”。 huber loss 在误差比较小的时候 $|a| \\le \\delta$ 表现的像是 L2 Loss，当误差比较大的时候 $|a| \u003e \\delta$ 表现的像是 L1 Loss，而对异常值具有更强的鲁棒性。\n",
  "wordCount" : "1713",
  "inLanguage": "en",
  "image": "https://wangjv0812.github.io/WangJV-Blog-Pages/","datePublished": "2025-04-18T16:40:25+08:00",
  "dateModified": "2025-04-18T16:40:25+08:00",
  "author":{
    "@type": "Person",
    "name": "WangJV"
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/"
  },
  "publisher": {
    "@type": "Organization",
    "name": "WangJV Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://wangjv0812.github.io/WangJV-Blog-Pages/favicon.ico"
    }
  }
}
</script>
</head>

<body class="" id="top">
<script>
    if (localStorage.getItem("pref-theme") === "dark") {
        document.body.classList.add('dark');
    } else if (localStorage.getItem("pref-theme") === "light") {
        document.body.classList.remove('dark')
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
        document.body.classList.add('dark');
    }

</script>

<header class="header">
    <nav class="nav">
        <div class="logo">
            <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" accesskey="h" title="WangJV Blog (Alt + H)">WangJV Blog</a>
            <div class="logo-switches">
                <button id="theme-toggle" accesskey="t" title="(Alt + T)" aria-label="Toggle theme">
                    <svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
                    </svg>
                    <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
                        fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
                        stroke-linejoin="round">
                        <circle cx="12" cy="12" r="5"></circle>
                        <line x1="12" y1="1" x2="12" y2="3"></line>
                        <line x1="12" y1="21" x2="12" y2="23"></line>
                        <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
                        <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
                        <line x1="1" y1="12" x2="3" y2="12"></line>
                        <line x1="21" y1="12" x2="23" y2="12"></line>
                        <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
                        <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
                    </svg>
                </button>
            </div>
        </div>
        <ul id="menu">
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/" title="Home">
                    <span>Home</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/" title="Posts">
                    <span>Posts</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/archives/" title="Archive">
                    <span>Archive</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/" title="Tags">
                    <span>Tags</span>
                </a>
            </li>
            <li>
                <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/search/" title="🔍 Search (Alt &#43; /)" accesskey=/>
                    <span>🔍 Search</span>
                </a>
            </li>
        </ul>
    </nav>
</header>
<main class="main">

<article class="post-single">
  <header class="post-header">
    <div class="breadcrumbs"><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">Home</a>&nbsp;»&nbsp;<a href="https://wangjv0812.github.io/WangJV-Blog-Pages/posts/">Posts</a></div>
    <h1 class="post-title entry-hint-parent">
      From Transformer to VGGT
    </h1>
    <div class="post-meta"><span title='2025-04-18 16:40:25 +0800 CST'>April 18, 2025</span>&nbsp;·&nbsp;9 min&nbsp;·&nbsp;1713 words&nbsp;·&nbsp;WangJV&nbsp;|&nbsp;<a href="https://github.com/WangJV0812/WangJV-Blog-Source/tree/master/content/posts/From%20Transformer%20to%20VGGT/index.md" rel="noopener noreferrer edit" target="_blank">Suggest Changes</a>

</div>
  </header> <div class="toc">
    <details >
        <summary accesskey="c" title="(Alt + C)">
            <span class="details">Table of Contents</span>
        </summary>

        <div class="inner"><nav id="TableOfContents">
  <ul>
    <li><a href="#1-preliminary-attention-and-vit">1. Preliminary: Attention and ViT</a>
      <ul>
        <li><a href="#11-encoder-and-decoder">1.1. Encoder and Decoder</a></li>
        <li><a href="#12-attention">1.2. Attention</a></li>
        <li><a href="#13-multi-head-attention">1.3. Multi-head Attention</a></li>
        <li><a href="#14-attention-的一些使用细节">1.4. Attention 的一些使用细节</a></li>
        <li><a href="#15-逐位置position-wise的前馈网络">1.5. 逐位置（position-wise）的前馈网络</a></li>
        <li><a href="#16-位置编码">1.6. 位置编码</a></li>
        <li><a href="#17-复杂度分析">1.7. 复杂度分析</a></li>
        <li><a href="#18-vision-transformer">1.8. Vision Transformer</a></li>
      </ul>
    </li>
    <li><a href="#2-vggt-introduction">2. VGGT Introduction</a></li>
    <li><a href="#3-method">3. Method</a>
      <ul>
        <li><a href="#31-问题定义和符号约定">3.1. 问题定义和符号约定</a></li>
        <li><a href="#32-特征提取骨干网络">3.2. 特征提取骨干网络</a></li>
        <li><a href="#33-交替注意力机制">3.3. 交替注意力机制</a></li>
        <li><a href="#34-预测头">3.4. 预测头</a></li>
      </ul>
    </li>
    <li><a href="#4-training">4. Training</a>
      <ul>
        <li><a href="#41-相机损失">4.1. 相机损失</a></li>
        <li><a href="#42-深度和点图损失">4.2. 深度和点图损失</a></li>
        <li><a href="#43-跟踪损失">4.3. 跟踪损失</a></li>
        <li><a href="#44-真值坐标系归一化">4.4. 真值坐标系归一化</a></li>
        <li><a href="#45-训练细节">4.5. 训练细节</a></li>
      </ul>
    </li>
    <li><a href="#5-experiments">5. Experiments</a></li>
    <li><a href="#references">References</a></li>
    <li><a href="#appendix">Appendix</a>
      <ul>
        <li><a href="#a-huber-loss">A. huber loss</a></li>
      </ul>
    </li>
  </ul>
</nav>
        </div>
    </details>
</div>

  <div class="post-content"><h2 id="1-preliminary-attention-and-vit">1. Preliminary: Attention and ViT<a hidden class="anchor" aria-hidden="true" href="#1-preliminary-attention-and-vit">#</a></h2>
<p>我们先来回顾一下经典的 Transformer 结构，之后从 Transformer 的角度来理解 ViT，这样大家能更好的理解 VGGT 和 MASt3R、DUSt3R 之类工作的苦恼之处。</p>
<p><img alt="VGGT Attention pipline" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20Attention%20pipline.png"></p>
<h3 id="11-encoder-and-decoder">1.1. Encoder and Decoder<a hidden class="anchor" aria-hidden="true" href="#11-encoder-and-decoder">#</a></h3>
<p>深度学习名著 《<a href="https://arxiv.org/pdf/1706.03762">Attention is all you need</a>》 提出的古典派 Attention（这么说是因为由于 Transformer 的大火，Attention 机制的变种已经太多了，我们只关注最经典的架构就好，其他都大同小异）。最经典的 Transformer 致力于解决翻译问题，是一个十分经典的 nlp 问题，采用了最经典的 Encoder-Decoder 结构。</p>
<p>Encoder 由 6 个完全相同的层堆叠而成，每个层由两个子层组成。第一个层负责实现 multi-head self-attention 机制；第二个层是一个简单的全连接前馈网络。为了避免在训练中出现梯度消失的问题，Transformer 在子层间采用了残差链接，之后对子层的输出做归一化（即 Add&amp;Norm）那个块。因此，每个子层的输出可以表示为：</p>
$$
\text{LayerNorm}(x+\text{Sublayer}(x))
$$<p>其中 $\text{Sublayer}(x)$ 是每个子层具体的实现。为了方便残差链接，每层的输出和输入（包括 embedding layers）都被约定为 $d_{module} = 512$。（至少 Attention is all you need 是这样的）。</p>
<p>Decoder 也是由完全相同的层堆叠而成，和 Encoder 不同的是 Decoder 的每个层也有三个子层。Decoder 的第一个子层是 masked multi-head self-attention，负责对输入的 embedding 做自注意力机制，之后将输入的 embedding 和 encoder 编码的结合结合起来。后面的层和 Encoder 一样，都是 multi-head self-attention 和前馈网络的组合。</p>
<h3 id="12-attention">1.2. Attention<a hidden class="anchor" aria-hidden="true" href="#12-attention">#</a></h3>
<p>注意力可以被看作一个函数，将 Querry、Key、Value 映射到一个输出上。Querry、Key、Value 和输出都是向量。事实上，输出可以看作一个对 Value 的加权输出。而具体如何用 Querry 和 Key 来加权 Value，就是注意力机制了，事实上这个权描述了对应的 Value 在这个任务上的重要程度。具体而言，此处使用的是 Scaled Dot-Product Attention。</p>
<p><img alt="Attention Mechanism" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20Attention%20mechanism.png"></p>
<p>上图左侧就是 Scaled Dot-Product Attention。我们假设输入 querry 和 key 的维度是 $d_k$，Value 的维度是 $d_v$，我们计算 querry 和所有 key 的点积并数乘以 $\frac{1}{\sqrt{d_k}}$，之后使用一个 softmax 来得到 value 的权重。事实上我们一堆 querry，key，value （batchsize）打包成矩阵 $Q, K, V$ 来加速计算。最终，矩阵形式的 Attention 可以写作：</p>
$$
\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$<p>事实上，Scaled Dot-Product Attention 并不是唯一的注意力即使（加权方法）。但是更多的 Attention mechanism，例如 点积注意力（dot-product attention）、$QKV$ 注意力（query-key-value attention）、强注意力（hard attention）、软注意力（soft attention）、自注意力（self attention）、交叉注意力（cross attention）、Luong 注意力、Bahdanau 注意力等。我们就不赘述。</p>
<h3 id="13-multi-head-attention">1.3. Multi-head Attention<a hidden class="anchor" aria-hidden="true" href="#13-multi-head-attention">#</a></h3>
<p>相比一个单头注意力机制直接解决，Multi-head Attention 机制将注意力机制分成了 $h$ 个单独的头，每个头通过三个对应的科学系的先行变换层将输入的 $QKV$ 变换到这个注意力头专用的 $QKV$ 空间中，分别投影到 $d_k, d_k, d_v$ 维度上。每个头上都进行经典的 Scaled Dot-Product Attention 计算。之后将这些头的输出结果（对于每个头而言，单个querry 而言，输出结果是一个标量）堆叠起来组成一个向量（对于 batch 的角度而言就是矩阵了），再通过一个可学习的线形映射层综合这些信息。</p>
<p>这些输出值会被拼接起来，然后再次进行投影，从而得到最终的值，如图2所示。</p>
<p>多头注意力机制使模型能够在不同位置同时关注来自不同表示子空间的信息。如果仅使用单个注意力头，这种信息的综合利用就会受到限制。</p>
$$
\begin{aligned}
\text{MultiHead}(Q, K, V) &= \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O\\
\text{where: } \text{head}_i &= \text{Attention}(QW^Q_i, KW^K_i, VW^V_i)
\end{aligned}
$$<p>这里的投影矩阵分别为：$W^Q_i \in R^{d_{model} \times d_k}$，$W^K_i \in R^{d_{model} \times d_k}$，$W^V_i \in R^{d_{model} \times d_v}$，以及 $W^O \in R^{hd_v \times d_{model}}$。</p>
<h3 id="14-attention-的一些使用细节">1.4. Attention 的一些使用细节<a hidden class="anchor" aria-hidden="true" href="#14-attention-的一些使用细节">#</a></h3>
<ul>
<li>需要注意的一点是，为了保证残差链接，encoder 中每一层的输出均为和输入相同维度的向量。因此事实上这张 pipline 是有些误导性的。其 $QKV$ 均为前一层的输出，事实上是通过多头注意力机制中的线形变换矩阵 $W^Q_i, W^K_i, W^V_i$ 变换到对应的 $QKV$ 的维度的。一般而言，我们称之为自注意力机制，其 $QKV$ 都是来源于同一个地方的</li>
<li>在 decoder 中的注意力处理与 encoder 有所不同，其 querry 来源于上一个 decoder 层的输出，keys 和 values 来源于 encoder 的输出。别忘了 Transformer 提出时是为了解决翻译问题，这样设计的好处是在自回归预测下一个词时，可以关注被翻译的句子的全部信息，同时为了保证句子的连贯性，decoder 中的注意力时 masked 的，即只能看到已经计算完成的信息。</li>
<li>同样地，解码器中的自注意力层允许解码器中的每个位置关注解码器中直至并包含该位置的所有位置。为了保持自回归特性，我们需要阻止解码器中的信息向左流动。我们通过在缩放点积注意力机制中，将 softmax 输入里所有对应于非法连接的值屏蔽掉（设为负无穷）来实现这一点。</li>
</ul>
<h3 id="15-逐位置position-wise的前馈网络">1.5. 逐位置（position-wise）的前馈网络<a hidden class="anchor" aria-hidden="true" href="#15-逐位置position-wise的前馈网络">#</a></h3>
<p>在每个 encoder 和 decoder 层中，除了注意力机制之外，还附加了一个全连接前馈网络。这个前馈网络会对输入的每个词操作。事实上这是一个非常简单的网络，只有两层，两层之间包含一个Relu。可以写作：</p>
$$
\text{FNN}(x) = max(0, xW_1 + b_1)W_2 + b_2
$$<p>尽管不同位置上的线性变换是相同的，但不同层之间使用的参数是不同的。我们可以将其视为两次核大小为 $1$ 的卷积操作。输入和输出的维度为 $d_{model}=512$，而中间层的维度为 $d_{ff}=2048$。</p>
<p>需要简单说明的是，这里的逐位置指的就是针对每个位置计算的 Value，都有一个对应的前馈网络。</p>
<h3 id="16-位置编码">1.6. 位置编码<a hidden class="anchor" aria-hidden="true" href="#16-位置编码">#</a></h3>
<p>因为 Transformer 中不包含任何循环或者卷积结构，任意顺序的输入对于 Transformer 而言都是平等的。其网络结构天然的不擅长捕捉序列间的相对位置关系。事实上也就因如此，Transformer 对并行化训练十分友好（以为各个位置的输入都是平等的，或者说相同的，先算谁后算谁都是一个道理）。我们必须给序列中引入一些标记，让其体现出输入的相对或绝对位置信息。为此，我们在编码器和解码器堆叠层底部的输入嵌入中添加了“位置编码”。位置编码与嵌入的维度相同，均为 $d_{model}$，这样二者就可以直接相加。</p>
<p>在这项工作中，我们使用不同频率的正弦和余弦函数：</p>
$$
PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i / d_{model}}}\right)\\
PE_{(pos, 2i + 1)} = \cos\left(\frac{pos}{10000^{2i / d_{model}}}\right)
$$<p>其中 $pos$ 表示位置，$i$ 表示维度。也就是说，位置编码的每个维度都对应一个正弦曲线。波长形成一个从 $2\pi$ 到 $10000\cdot 2\pi$ 的等比数列。我们选择这个函数，是因为我们推测它能让模型轻松地通过相对位置来学习注意力机制，这是因为对于任何固定的偏移量 $k$，$PE_{pos + k}$ 都可以表示为 $PE_{pos}$ 的线性函数。</p>
<p><img alt="VGGT Transformer visualization" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20Transformer%20visualization.png"></p>
<h3 id="17-复杂度分析">1.7. 复杂度分析<a hidden class="anchor" aria-hidden="true" href="#17-复杂度分析">#</a></h3>
<table>
  <thead>
      <tr>
          <th>Layer Type</th>
          <th>Complexity per Layer</th>
          <th>Sequential Operations</th>
          <th>Maximum Path Length</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Self - Attention</td>
          <td>$O(n^2 \cdot d)$</td>
          <td>$O(1)$</td>
          <td>$O(1)$</td>
      </tr>
      <tr>
          <td>Recurrent</td>
          <td>$O(n \cdot d^2)$</td>
          <td>$O(n)$</td>
          <td>$O(n)$</td>
      </tr>
      <tr>
          <td>Convolutional</td>
          <td>$O(k \cdot n \cdot d^2)$</td>
          <td>$O(1)$</td>
          <td>$O(\log_k(n))$</td>
      </tr>
      <tr>
          <td>Self - Attention (restricted)</td>
          <td>$O(r \cdot n \cdot d)$</td>
          <td>$O(1)$</td>
          <td>$O(\frac{n}{r})$</td>
      </tr>
  </tbody>
</table>
<p>事实上，Transformer 的改进可太多了，例如应对巨量文本输入的稀疏注意力、对注意力计算结果缓存（KV Cache、Flash Attention）、针对位置编码的改进（RoPE）等等等等。要全部讨论这些可能需要不止一次 Seminar 我们后面找机会再讨论吧。</p>
<h3 id="18-vision-transformer">1.8. Vision Transformer<a hidden class="anchor" aria-hidden="true" href="#18-vision-transformer">#</a></h3>
<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20ViT%20pipline.png"></p>
<p><img alt="alt text" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20Vision_Transformer.gif"></p>
<p>ViT将输入图片分为多个patch（16x16），再将每个patch投影为固定长度的向量送入Transformer，后续encoder的操作和原始Transformer中完全相同。但是因为对图片分类，因此在输入序列中加入一个特殊的token，该token对应的输出即为最后的类别预测。按照上面的流程，ViT 可以分为下面几个步骤：</p>
<ol>
<li>Path embedding：例如输入照片的尺寸为 $244 \times 244$，之后将图片分为大小为 $16 \times 16$ 的 Patch。因此每个图片会产生 $\frac{244}{16} \times \frac{244}{16} = 196$ patch。按照传统 Transformer 的话来说，就是输入序列的长度为 196。每个 patch 或者说 embedding 的维度为 $16\times 16\times 3 = 768$，线形投影层的维度为 $768 \times N$。那么通过线形投影层变换后的维度为 $196\times N$。即 $196$ 个 token，每个 token 的维度为 $N$。但是还没完，最终还需要添加一个特殊的token cls 标记序列结束了。所以此时 attention mecanism 输入的维度为 $197\times N$。不难发现，此时已将一个图像处理问题转换为一个 seq2seq 问题。</li>
<li>Transformer 的对序列相对/绝对关系的缺点（或者说是优点）依然存在，我们需要给每个图片的 patch 编码。此处的方法和 Transformer 完全相同。对于每个 patch 生成一个只与其位置有关，维度为 $N$ 的编码，直接加到 patch embedding 上。这样就能保留 patch 的位置信息了。</li>
<li>multihead attention。对于多个头的情况（例如 12 个头），ViT 会通过一个线形映射将输入的 embedding 降维映射到维度为 $N // 12$ 的 QKV 上，每个头处理后再将输出拼接起来，还原为原始的维度 $N$。之后通过一个先变大后变小的 MLP。最终每层输出的维度依然为 $197\times N$</li>
</ol>
$$
\begin{array}{ll}
z_{0}=\left[x_{class } ; x_{p}^{1} E ; x_{p}^{2} E ; \cdots ; x_{p}^{N} E\right]+E_{pos}, &E \in \mathbb{R}^{(P^{2} \cdot C) \times D}, E_{pos} \in \mathbb{R}^{(N + 1) \times D}\\
z_{\ell}'=\text{MSA}(\text{LN}(z_{\ell - 1})) + z_{\ell - 1}, &\quad \ell = 1 \cdots L\\
z_{\ell}=\text{MLP}(\text{LN}(z_{\ell}')) + z_{\ell}', &\quad \ell = 1 \cdots L\\
y=\text{LN}(z_{L}^{0})
\end{array}
$$<p>读到这里你可能会想，为什么 ViT 这样一个显然的思路之前没人做呢？事实上当网络的规模不够大时，ViT 的效果是不不过 ResNet 或者 CNN 的。但是当训练数据集不够大的时候，ViT的表现通常比同等大小的ResNets要差一些，因为Transformer和CNN相比缺少归纳偏置（inductive bias），即一种先验知识，提前做好的假设。</p>
<p>CNN具有两种归纳偏置：</p>
<ul>
<li>一种是局部性（locality/two-dimensional neighborhood structure），即图片上相邻的区域具有相似的特征</li>
<li>一种是平移不变性（translation equivariance），$f(g(x)) = g(f(x))$ ，其中 $g$ 代表卷积操作，$f$ 代表平移操作。</li>
</ul>
<p>当CNN具有以上两种归纳偏置，就有了很多先验信息，需要相对少的数据就可以学习一个比较好的模型。而当数据量和网络规模变大时，Transformer 的优势才体现出来。ViT 则相信 Transformer 的表达能力，即使没有归纳偏置，也能从数据中学习到足够的先验知识。ViT 通过大规模数据集的训练，获得了比 CNN 更好的性能。</p>
<p><img alt="ViT Attention" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20ViT%20Attention.png"></p>
<h2 id="2-vggt-introduction">2. VGGT Introduction<a hidden class="anchor" aria-hidden="true" href="#2-vggt-introduction">#</a></h2>
<p>VGGT 是一个大型的前馈网络，通过输入一张照片到多张照片（甚至几百张）作为输入；直接推理出一个场景几乎所有的三维特征，包括相机矩阵、点图（point map）、深度图和三维点跟踪（3D Point Tracks）。传统的三维重建方法通常基于视觉几何，使用例如 BA 之类迭代优化的方法。机器学习常常扮演着重要的补充角色，用于处理仅靠几何方法无法解决的任务，比如特征匹配和单目出深度。二者的融合越来越紧密，如今像 VGGSfM 这样 SOTA 的 SfM 方法，通过可微光束法平差将机器学习和视觉几何进行端到端的结合。即便如此，视觉几何在三维重建中仍起着主要作用，这增加了问题的复杂性和计算成本。</p>
<p>本文作者发现，没有必要为三维重建任务设计一个专用的网络结构，VGGT 直接使用了标注你的大型 Transformer，没有使用特殊的三维或其他归纳偏置，而是用一种通用的网络结构在极大量的有三维标注的公开图像数据集上训练。和其他针对三维任务的大型神经网络，例如 DepthAnything、MoGe、LRM 等只针对一个特定的三维任务（例如单目深度估计）。VGGT 使用一个共享的骨干网络来预测所有感兴趣的三维特征，在推理过程中可以从预测出的深度图和相机矩阵计算出 pointmap，与直接使用专门的 pointmap 预测头相比，能获得更高的精度。</p>
<p>总而言之，这篇文章的贡献如下</p>
<ol>
<li>引入了视觉几何基础变换器（VGGT），这是一个大型的前馈 Transformer。给定一个场景的单张、几张甚至数百张图像，它能够在数秒内预测出该场景的所有关键三维属性，包括相机的<strong>内参和外参</strong>、<strong>点云图</strong>、<strong>深度图</strong>以及<strong>三维点轨迹</strong>。</li>
<li>VGGT 的预测结果可直接使用，其竞争力很强，并且通常优于那些使用缓慢的后处理优化技术的最先进方法的预测结果。</li>
<li>当进一步结合光束法平差（BA）后处理时，即使与那些专门针对某一类三维任务的方法相比，VGGT 也全面取得了领先的成果，并且常常能大幅提升任务处理的质量。</li>
</ol>
<h2 id="3-method">3. Method<a hidden class="anchor" aria-hidden="true" href="#3-method">#</a></h2>
<p><img alt="VGGT pipline" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20pipline.png"></p>
<h3 id="31-问题定义和符号约定">3.1. 问题定义和符号约定<a hidden class="anchor" aria-hidden="true" href="#31-问题定义和符号约定">#</a></h3>
<p>对于一个输入网络侧图片序列 $(I_i)^N_{i = 1}$，表示 $N$ 张 RGB 图片 $I_i \in \mathbb R^{3\times H \times W}$，我们默认这些图片是对同一个场景的观测。VGGT 使用的 Transformer 认为也是一个映射，将这个图片序列映射到这个三维场景的描述，对于每一帧，这个映射可以写作：</p>
$$
f\left(\left(I_{i}\right)_{i = 1}^{N}\right)=\left(g_{i}, D_{i}, P_{i}, T_{i}\right)_{i = 1}^{N}
$$<p>这个 Transformer 将每张图像 $I_i$ 映射为其相机参数 $g_i \in \mathbb{R}^9$，包含内参和外参；深度图 $D_i \in \mathbb{R}^{H×W}$；其点图 $P_i \in \mathbb{R}^{3×H×W}$；以及一个用于点匹配的的 $C$ 维特征 $T_i \in \mathbb{R}^{C×H×W}$。接下来我们将解释这些量是如何定义的。</p>
<p>相机参数 $g_i$ 定义为：$g = [\bf q, \bf t, \bf f]$，是将描述旋转的四元数 $\bf q \in \mathbb R^4$，描述平移的向量 $\bf t \in \mathbb R^3$ 和焦距 $\bf f \in \mathbb R^2$ 组合在一起的。此处我们假设了相机为针孔模型。</p>
<p>我们定义图片 $I_i$ 的定义域为 $\mathcal I(I_i) = \{1, 2, \dots, H\}\times \{1, 2, \dots, W\}$，是所有可能的像素的集合。深度图 $D_{i}$ 将每个像素位置 $y \in I(I_{i})$ 与其对应的深度值 $D_{i}(y) \in \mathbb{R}^{+}$ 相关联，该深度值是从第 $i$ 个相机观测到的。类似的，pointmap $P_{i}$ 将每个像素与其对应的三维场景点 $P_{i}(y) \in \mathbb{R}^{3}$ 相关联。与 DUSt3R 一致，三维点 $P_{i}(y)$ 的坐标系为第一个相机 $g_{1}$ 的坐标系中。换句话说，我们认为第一个相机 $g_{1}$ 的坐标系为世界坐标系</p>
<p>最后，关于关键点跟踪（point tracking）。对于图像 $I_q$ 中的一个固定点 $y_q$，网络将输出一个跟踪 $\mathcal T^\star (y_q) = (y_i)^N_{i = 1}$，即其他所有图片上的对应点。需要注意的是，网络并不会直接输出轨迹，而是输出用于跟踪的特征 $T_i \in \mathbb{R}^{C \times H \times W}$。具体的点的跟踪将会由一个独立的网络实现：</p>
$$
T \left( (y_j)_{j=1}^M, (T_i)_{i=1}^N \right) = \left( (\hat{y}_{j,i})_{i=1}^N \right)_{j=1}^M
$$<p>这个网络 $\mathcal T$ 输入跟踪点 $y_q$ 和稠密的跟踪 feather $T_i$，计算对应的其他照片中的对应点。跟踪网络 $\mathcal T$ 和主网络 $f$ 是一起端到端训练的。</p>
<p>在推理和训练时，除了第一张照片的输入需要特别注意意外，其他照片可以以任意顺序输入。因为第一张照片将作为参考坐标系。VGGT 的网络结构在设计时特意考虑了这一点，让除了第一张照片以外的任意输入都是等价的。在后面网络结构的介绍中我们将看到这一点。</p>
<p>事实上，VGGT 的网络具有 &ldquo;over-complete prediction&rdquo; 的性质。换句话说，它的有些预测是相关的，例如预测的相机参数 $\bf g$ 可以从不变的 pointmap $P$ 计算出。此外，深度图还可以从 pointmap 和相机矩阵推出。事实上正是这种预测结果之间的闭式关系让我们实现预测量之间的相互监督，实现了显著的心梗提升。事实上与直接专门预测 pointmap 的网络相比，结合相机参数和深度图的相互监督可以提升 pointmap 的预测精度。</p>
<h3 id="32-特征提取骨干网络">3.2. 特征提取骨干网络<a hidden class="anchor" aria-hidden="true" href="#32-特征提取骨干网络">#</a></h3>
<p>事实上，VGGT 通过一个巨大的 Transformer （1.2B）实现了映射 $f$ 的功能：</p>
$$
f\left(\left(I_{i}\right)_{i = 1}^{N}\right)=\left(g_{i}, D_{i}, P_{i}, T_{i}\right)_{i = 1}^{N}
$$<p>首先，VGGT 先通过 DINO 将图片 $I$ 分割为一组 $K$ 个 token $t^I \in \mathbb R^{K\times C}$。之后，所有帧的 token 组的并 $t^I = \cup^N_{i = 1}\{t_i^I\}$ 通过骨干网络来处理，进行全局自注意力。</p>
<h3 id="33-交替注意力机制">3.3. 交替注意力机制<a hidden class="anchor" aria-hidden="true" href="#33-交替注意力机制">#</a></h3>
<p>VGGT 引入了交替注意力机制对经典的 Transformer 的注意力机制做了调整，使 Transformer 能够以交替的方式聚焦于每个帧内和全局的信息。具体而言，帧级的注意力机制单独处理每个帧内的 token $t_k^I$，而全局注意力则将所有帧的 token 拼接在一起 $t_I$ 处理。这种设计在整合不同图像间信息与归一化每个图像内令牌的激活值之间取得了平衡。本文中使用 $L=24$ 层的全局-局部注意力机制。</p>
<p>具体的 Alternative Attention Transformer 的机制论文中没有说的非常明白，我们不妨回到论文，看看具体是怎么做的。首先是 global attention 和 frame attention 的定义，我们此处不贴具体代码了，只说结论，global attention 和 frame attention 的 block 是完全相同的。在具体处理时：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-py" data-lang="py"><span class="line"><span class="cl"><span class="bp">self</span><span class="o">.</span><span class="n">aa_order</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;frame&#34;</span><span class="p">,</span> <span class="s2">&#34;global&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="err">·······</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">aa_block_num</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">attn_type</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">aa_order</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">attn_type</span> <span class="o">==</span> <span class="s2">&#34;frame&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">tokens</span><span class="p">,</span> <span class="n">frame_idx</span><span class="p">,</span> <span class="n">frame_intermediates</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_frame_attention</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">tokens</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">frame_idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">pos</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">attn_type</span> <span class="o">==</span> <span class="s2">&#34;global&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">tokens</span><span class="p">,</span> <span class="n">global_idx</span><span class="p">,</span> <span class="n">global_intermediates</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_process_global_attention</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">tokens</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">global_idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">pos</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Unknown attention type: </span><span class="si">{</span><span class="n">attn_type</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">frame_intermediates</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># concat frame and global intermediates, [B x S x P x 2C]</span>
</span></span><span class="line"><span class="cl">        <span class="n">concat_inter</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">cat</span><span class="p">([</span><span class="n">frame_intermediates</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">global_intermediates</span><span class="p">[</span><span class="n">i</span><span class="p">]],</span> <span class="n">dim</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">output_list</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">concat_inter</span><span class="p">)</span>
</span></span></code></pre></div><p>其中 <code>B</code> 表示 batch size，即批量大小，<code>S</code> 表示 sequence length，即图片序列长度。<code>P</code> 表示每张照片的 patch 数量，<code>C</code> 表示每个 patch 的维度。具体的 frame attention 和 global attention 的实现细节如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-py" data-lang="py"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_process_frame_attention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tokens</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">frame_idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Process frame attention blocks. We keep tokens in shape (B*S, P, C).
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># If needed, reshape tokens or positions:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">tokens</span><span class="o">.</span><span class="n">shape</span> <span class="o">!=</span> <span class="p">(</span><span class="n">B</span> <span class="o">*</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">tokens</span> <span class="o">=</span> <span class="n">tokens</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span> <span class="o">*</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pos</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">pos</span><span class="o">.</span><span class="n">shape</span> <span class="o">!=</span> <span class="p">(</span><span class="n">B</span> <span class="o">*</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">pos</span> <span class="o">=</span> <span class="n">pos</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span> <span class="o">*</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">intermediates</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># by default, self.aa_block_size=1, which processes one block at a time</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">aa_block_size</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">tokens</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">frame_blocks</span><span class="p">[</span><span class="n">frame_idx</span><span class="p">](</span><span class="n">tokens</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">pos</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">frame_idx</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="n">intermediates</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tokens</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">tokens</span><span class="p">,</span> <span class="n">frame_idx</span><span class="p">,</span> <span class="n">intermediates</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_process_global_attention</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">tokens</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">global_idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Process global attention blocks. We keep tokens in shape (B, S*P, C).
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">tokens</span><span class="o">.</span><span class="n">shape</span> <span class="o">!=</span> <span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span> <span class="o">*</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">tokens</span> <span class="o">=</span> <span class="n">tokens</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span> <span class="o">*</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pos</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span> <span class="ow">and</span> <span class="n">pos</span><span class="o">.</span><span class="n">shape</span> <span class="o">!=</span> <span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span> <span class="o">*</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">pos</span> <span class="o">=</span> <span class="n">pos</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span> <span class="o">*</span> <span class="n">P</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="err">········</span> <span class="n">和上一个函数完全相同</span><span class="err">，</span><span class="n">就不赘述了</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">tokens</span><span class="p">,</span> <span class="n">global_idx</span><span class="p">,</span> <span class="n">intermediates</span>
</span></span></code></pre></div><p>这样，我们可以缕出 alternative attention 的完整逻辑。实际上不论是 frame attention 还是 global attention，都是一个完整的 transformer block。对于 VGGT 的每一层，输入的 token 先通过 frame attention 处理，之后再通过 global attention 处理。（此处和论文有所不同，论文 pipline 是先经过 global attention 后 frame attention），以此往复 24 次。</p>
<p>事实上 global attention 和 frame attention的实现也十分简单。global attention 实际上是将所有的帧的 tokne 堆叠在一起处理做自注意力，而 frame attention 则是将帧当作 batch 处理，也就是代码中的：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-py" data-lang="py"><span class="line"><span class="cl"><span class="n">tokens</span> <span class="o">=</span> <span class="n">tokens</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span> <span class="o">*</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tokens</span> <span class="o">=</span> <span class="n">tokens</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span><span class="p">,</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span><span class="o">.</span><span class="n">view</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">S</span> <span class="o">*</span> <span class="n">P</span><span class="p">,</span> <span class="n">C</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="34-预测头">3.4. 预测头<a hidden class="anchor" aria-hidden="true" href="#34-预测头">#</a></h3>
<p>下面我们要讨论 VGGT 是如何通过 Transformer 和预测头实现这神乎其神的功能。</p>
<p>首先，VGGT 在每张图片用 DINO pachify 的 token $t_i^I$ 的基础上，给每个图片都添加了一个额外的相机 token $t_i^g\in \mathbb R^{1\times C^\prime}$ 和 4 个寄存器 token $t_i^R\in \mathbb R^{4\times C^\prime}$，由此，每个图片的 token 可以表达为：$\{t_i^g, t_i^R, t_i^I\}$。这些添加的 5个 token 是网络的可学习参数。</p>
<p>这里原论文没有说明白的一点是，事实上，我们只添加了两组 camera token 和 register token。第一组 camera token 是第一帧专项的，记做：</p>
$$
\begin{array}{l}
t_1^g = \overline{t}^g& t_1^R = \overline{t}^R
\end{array}
$$<p>其他所有帧公用另外一组 camera token 和 register token，记做：</p>
$$
\begin{array}{cc}
\begin{aligned}
t_i^g &= \overline{\overline{t}}^g\\
t_i^R &= \overline{\overline{t}}^R
\end{aligned}
&\forall i \in \{2,3,\dots,N\} \\
\end{array}
$$<p>camera token 和 register token 经过网络预测记做：</p>
$$
\begin{aligned}
f(t_i^g) &= \hat{t_i^g}\\
f(t_i^R) &= \hat{t_i^R}\\
f(t_i^I) &= \hat{t_i^I}\\
\end{aligned}
$$<p>在预测相机参数时，我们只使 $\hat{t_i^g}$，register token 被抛弃了。</p>
<h4 id="341-坐标系">3.4.1 坐标系<a hidden class="anchor" aria-hidden="true" href="#341-坐标系">#</a></h4>
<p>如前所述，我们在第一台相机 $g_{1}$ 的坐标系中预测相机参数、点图和深度图。因此，第一台相机的输出相机外参被设置为单位矩阵，即第一个旋转四元数为 $q_{1}=[0,0,0,1]$，第一个平移向量为 $t_{1}=[0,0,0]$。回顾可知，特殊的相机令牌和寄存器令牌 $t_{1}^{g}:=\overline{t}^{g}$、$t_{1}^{R}:=\overline{t}^{R}$ 使 Transformer 能够识别第一台相机。</p>
<h4 id="342-相机参数预测头">3.4.2 相机参数预测头<a hidden class="anchor" aria-hidden="true" href="#342-相机参数预测头">#</a></h4>
<p>VGGT 通过刚才添加的相机 token 预测出的 token $(\hat t_u^g)_{i=1}^N$ 来预测相机参数 $(\hat g^i)_{i=1}^N$。$(\hat t_u^g)_{i=1}^N$ 通过连接 4 个额外的连接了线形层的自注意力层进行预测，这构成了相机内参的预测头。</p>
<p>事实上使用的网络比论文描述的稍微复杂不少。相机参数预测头会多轮优化预测的结果。大致的 pipline 可以被描述为：</p>
<ul>
<li>当一个新的迭代开始时，先会使用一个全为 0 的位置编码，通过一个线形映射投影到 token 的维度</li>
<li>如果这不是第一轮，之前已经有了迭代结构，则会用上一轮的优化结果映射到 token 的维度（当然这里需要detach，避免梯度回传），叫做 <code>module_input</code>。</li>
<li>之后会通过一个 SiLU 网络和一个很浅的全连接调整维度，将 <code>module_input</code>映射到 <code>shift</code>, <code>scale</code>和 <code>gate</code>。</li>
<li>之后用 shift，scale 对归一化的 $(\hat g^i)_{i=1}^N$ 做仿射变换，用于体现之前优化的结构</li>
</ul>
$$
\text{pose\_tokens\_modulated} = (1 + scale) * (\hat g^i)_{i=1}^N + shift
$$<ul>
<li>对 <code>pose_tokens_modulated</code> 和 $(\hat g^i)_{i=1}^N$  残差连接，之后进行 transformer</li>
<li>最后进行全连接调整维度和预测</li>
<li>最终将调整的结果加在上一轮迭代结果上</li>
</ul>
<p>事实上，这样做是因为 Transformer 更擅长与处理上下文语意，而相机参数恰好是一个尺度敏感的参数。因此此处用了一个 学习的放射变换来调整。</p>
<h4 id="343-稠密预测头">3.4.3 稠密预测头<a hidden class="anchor" aria-hidden="true" href="#343-稠密预测头">#</a></h4>
<p>输出的图像 token $\hat t^I_i$ 用于预测稠密输出的深度图 $D_i$、点图 $P_i$ 和用于跟踪的特征 $T_i$。更具体的说，VGGT 首先将 $\hat t_i^I$ 通过 DPT 层转换为较为稠密的特征图 $F_i \in \mathbb R^{C^{\prime \prime} \times H\times W}$，之后 $F_i$ 通过一个 $3 \times 3$ 的卷积层映射到对应的深度图 $D_i$ 和 $P_i$。此外，DPT 还输出了稠密预测特征 $T_i \in \mathbb R^{C\times H \times W}$，将输入后面的跟踪头。</p>
<p>此外，VGGT 还预测出了每个深度图和 pointmap 的不确定性图 $\Sigma_i^D, \Sigma_i^P \in \mathbb R_+^{H\times W}$。这个不确定性图和 MASt3R 相同，用于加权的计算损失。</p>
<p><img alt="DPT pipline" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20DPT.png"></p>
<h4 id="344-跟踪头">3.4.4 跟踪头<a hidden class="anchor" aria-hidden="true" href="#344-跟踪头">#</a></h4>
<p>跟踪网络 $\mathcal T$ 采用了 CoTracker2 架构。对于一个等待 track 的三维点集 $y_j$ 和这个点对应的图片提取出的特征 $T_i$，跟踪网络 $\mathcal T$ 将会计算出所有的照片中 $y_j$ 对应的二维点。</p>
$$
T\big((y_j)_{j = 1}^M, (T_i)_{i = 1}^N\big)=((\hat{y}_{j,i})_{i = 1}^N)_{j = 1}^M
$$<p>通过上面的形式可以看出相对于三维点而言，骨干网络提取出的特征 $T_i$ 可能是不对齐的，因此首先我们要在每个查询点 $y_j$ 上对 $T_i$ 双线形差值。然后将该特征与其他特征图 $T_i$ 进行关联，得到一组关联图。接着通过自注意力机制对关联图处理，以预测出最终对应的二维点 $\hat y_i$。</p>
<h2 id="4-training">4. Training<a hidden class="anchor" aria-hidden="true" href="#4-training">#</a></h2>
<p>训练时，VGGT 使用了如下的 lossfunction：</p>
$$
\mathcal L = \mathcal L_{\text{camera}} + \mathcal L_{\text{pmap}} + \lambda\mathcal L_{\text{track}}
$$<p>有趣的是，VGGT 训练时发现 $\mathcal L_{\text{camera}}$ 和 $\mathcal L_{\text{pmap}}$ 的尺度是相似的，因此不需要通过缩放因子来调整。但是 $\mathcal L_{\text{track}}$ 不同，需要调整。一般而言有 $\lambda = 0.05$。</p>
<h3 id="41-相机损失">4.1. 相机损失<a hidden class="anchor" aria-hidden="true" href="#41-相机损失">#</a></h3>
<p>相机损失 $\mathcal L_{\text{camera}}$ 用于监督相机参数 $\hat{\bf g}$:</p>
$$
\mathcal L_{\text{camera}} = \sum_{i=1}^N \|\hat{\bf g}_i - \bf g_i \|_\epsilon
$$<p>其中 $\| \cdot \|_\epsilon$ 是 huber 损失。</p>
<h3 id="42-深度和点图损失">4.2. 深度和点图损失<a hidden class="anchor" aria-hidden="true" href="#42-深度和点图损失">#</a></h3>
<p>深度损失 $\mathcal L_{\text{depth}}$ 和 DUSt3R 相同，使用不确定性图加权了预测深度图 $\hat D_i$ 与 真值 $D_i$ 之间的残差。与 DUSt3R 不同的是，VGGT 还引入了单目深度估计中的图像梯度项，损失可以被写作：</p>
$$
L_{\text{depth}} = \sum_{i=1}^{N} \|\Sigma_i^D \odot (\hat{D}_i - D_i)\| + \|\Sigma_i^D \odot (\nabla \hat{D}_i - \nabla D_i)\| - \alpha \log \Sigma_i^D
$$<p>其中 $\odot$ 表示通道广播逐元素乘积（Channel-broadcast element-wise product）（看着这个名字很吓人，实际上就是数乘，因为 $\Sigma_i^D\in \mathbb R^{H\times W}$，而 $\nabla \hat{D}_i\in \mathbb R^{H\times W\times 2}，需要乘到向量上$ ）。$\alpha$ 是一个超参数。</p>
<p>point map 的损失和深度损失很类似，可以写作：</p>
$$
L_{\text{pmap}} = \sum_{i=1}^{N} \|\Sigma_i^P \odot (\hat{P}_i - P_i)\| + \|\Sigma_i^P \odot (\nabla \hat{P}_i - \nabla P_i)\| - \alpha \log \Sigma_i^P
$$<h3 id="43-跟踪损失">4.3. 跟踪损失<a hidden class="anchor" aria-hidden="true" href="#43-跟踪损失">#</a></h3>
<p>跟踪损失可以写作：</p>
$$
L_{\text{track}} = \sum_{j=1}^{M} \sum_{i=1}^{N} \|y_{j,i} - \hat{y}_{j,i}\|
$$<p>其中外层求和是遍历查询图像 $I_q$ 所有的查询点 $y_j$，内层循环遍历所有可能的照片。$y_{j,i}$ 为在图像 $i$ 中的真实点。$\hat{y}_{j,i}$ 为跟踪模块的预测结果 $T((y_j)_{j=1}^{M}, (T_i)_{i=1}^{N})$。</p>
<h3 id="44-真值坐标系归一化">4.4. 真值坐标系归一化<a hidden class="anchor" aria-hidden="true" href="#44-真值坐标系归一化">#</a></h3>
<p>如果我们缩放一个场景或者改变其参考系，这个场景的照片事实上不会有任何变换。这意味着改变参考系和缩放在三维重建上是正常的，或者说等价的，换句话说就是三维重建的尺度不一致性。这种不同图片预测出的结果的尺度不一致性是无法自然的通过损失来监督的，因此要对真值做归一化。这个归一化事实上分为两步：</p>
<ol>
<li>将所有照片的坐标系转换到第一个照片的坐标系中</li>
<li>计算该场景中所有三维点在异地个照片的坐标系中，距离坐标原点的平均欧几里得距离。用这个平均距离来归一化相机外参 $t$ 深度图 $D$ 和点图 $P$。</li>
</ol>
<p>一个很有趣的细节，或者说和 M/DUSt3R 的不同之处是，VGGT 不会对预测结果归一化，而是迫使模型从数据集学习这种归一化的方式</p>
<h3 id="45-训练细节">4.5. 训练细节<a hidden class="anchor" aria-hidden="true" href="#45-训练细节">#</a></h3>
<p>实现细节。默认情况下，我们分别使用 $L = 24$ 层的全局注意力和帧级注意力。该模型总共约有 1.2B 参数。我们通过 AdamW 优化器对训练损失进行 16 万轮优化来训练模型。我们使用余弦学习率调度器，峰值学习率为 0.0002，预热 8000 轮。对于每个批次，我们从随机选取的训练场景中随机采样 2 - 24 帧。输入的帧、深度图和点图被调整为最大尺寸为 518 像素。宽高比在 0.33 到 1.0 之间随机设定。我们还对帧随机应用颜色抖动、高斯模糊和灰度增强等操作。训练在 64 块 A100 GPU 上进行，持续九天。我们采用梯度范数裁剪，阈值设为 1.0，以确保训练的稳定性。我们利用 bfloat16 精度和梯度检查点技术来提高 GPU 内存和计算效率。</p>
<h2 id="5-experiments">5. Experiments<a hidden class="anchor" aria-hidden="true" href="#5-experiments">#</a></h2>
<p><img alt="VGGT pointmap Visualization" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20pointmap.png"></p>
<p><img alt="VGGT track and VGGT enhanced CoTracker" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT%20track%20result.png"></p>
<p><img alt="VGGT one shot result" loading="lazy" src="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/04/from-transformer-to-vggt/Images/VGGT_oneshot.png"></p>
<h2 id="references">References<a hidden class="anchor" aria-hidden="true" href="#references">#</a></h2>
<p><a href="https://arxiv.org/pdf/1706.03762">Attention is all you need</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/445122996">ViT（Vision Transformer）解析</a></p>
<p><a href="https://arxiv.org/pdf/2010.11929">AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE</a></p>
<p><a href="https://en.wikipedia.org/wiki/Vision_transformer">wiki vision transformer</a></p>
<p><a href="https://arxiv.org/abs/2503.11651">VGGT: Visual Geometry Grounded Transformer</a></p>
<p><a href="https://github.com/facebookresearch/vggt">VGGT opensource coed in github</a></p>
<p><a href="https://arxiv.org/abs/2103.13413">Vision Transformer for Dense Prediction</a></p>
<h2 id="appendix">Appendix<a hidden class="anchor" aria-hidden="true" href="#appendix">#</a></h2>
<h3 id="a-huber-loss">A. huber loss<a hidden class="anchor" aria-hidden="true" href="#a-huber-loss">#</a></h3>
$$
L_\delta(a) =
\begin{cases}
\frac{1}{2} a^2, & \text{if } |a| \leq \delta \\
\delta(|a| - \frac{1}{2} \delta), & \text{if } |a| > \delta
\end{cases}
$$<p>其中：</p>
<ul>
<li>$a = y - \hat{y}$ 表示预测值和真实值的差；</li>
<li>$\delta$ 是一个超参数，控制“哪些差值被当作离群点”。</li>
</ul>
<p>huber loss 在误差比较小的时候 $|a| \le \delta$ 表现的像是 L2 Loss，当误差比较大的时候 $|a| > \delta$ 表现的像是 L1 Loss，而对异常值具有更强的鲁棒性。</p>


  </div>

  <footer class="post-footer">
    <ul class="post-tags">
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/transformer/">Transformer</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/vit/">ViT</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/vggt/">VGGT</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E4%B8%89%E7%BB%B4%E9%87%8D%E5%BB%BA/">三维重建</a></li>
      <li><a href="https://wangjv0812.github.io/WangJV-Blog-Pages/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/">计算机视觉</a></li>
    </ul>
<nav class="paginav">
  <a class="prev" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/06/3d-kinematics-and-dynamics/">
    <span class="title">« Prev</span>
    <br>
    <span>3D Kinematics and Dynamics</span>
  </a>
  <a class="next" href="https://wangjv0812.github.io/WangJV-Blog-Pages/2025/03/dust3r-and-must3r/">
    <span class="title">Next »</span>
    <br>
    <span>DUSt3R and MUSt3R</span>
  </a>
</nav>

  </footer>
</article>
    </main>
    
<footer class="footer">
        <span>&copy; 2025 <a href="https://wangjv0812.github.io/WangJV-Blog-Pages/">WangJV Blog</a></span> · 

    <span>
        Powered by
        <a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
        <a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
    </span>
</footer>
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
        <path d="M12 6H0l6-6z" />
    </svg>
</a>

<script>
    let menu = document.getElementById('menu')
    if (menu) {
        menu.scrollLeft = localStorage.getItem("menu-scroll-position");
        menu.onscroll = function () {
            localStorage.setItem("menu-scroll-position", menu.scrollLeft);
        }
    }

    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener("click", function (e) {
            e.preventDefault();
            var id = this.getAttribute("href").substr(1);
            if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
                    behavior: "smooth"
                });
            } else {
                document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
            }
            if (id === "top") {
                history.replaceState(null, null, " ");
            } else {
                history.pushState(null, null, `#${id}`);
            }
        });
    });

</script>
<script>
    var mybutton = document.getElementById("top-link");
    window.onscroll = function () {
        if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
            mybutton.style.visibility = "visible";
            mybutton.style.opacity = "1";
        } else {
            mybutton.style.visibility = "hidden";
            mybutton.style.opacity = "0";
        }
    };

</script>
<script>
    document.getElementById("theme-toggle").addEventListener("click", () => {
        if (document.body.className.includes("dark")) {
            document.body.classList.remove('dark');
            localStorage.setItem("pref-theme", 'light');
        } else {
            document.body.classList.add('dark');
            localStorage.setItem("pref-theme", 'dark');
        }
    })

</script>
<script>
    document.querySelectorAll('pre > code').forEach((codeblock) => {
        const container = codeblock.parentNode.parentNode;

        const copybutton = document.createElement('button');
        copybutton.classList.add('copy-code');
        copybutton.innerHTML = 'copy';

        function copyingDone() {
            copybutton.innerHTML = 'copied!';
            setTimeout(() => {
                copybutton.innerHTML = 'copy';
            }, 2000);
        }

        copybutton.addEventListener('click', (cb) => {
            if ('clipboard' in navigator) {
                navigator.clipboard.writeText(codeblock.textContent);
                copyingDone();
                return;
            }

            const range = document.createRange();
            range.selectNodeContents(codeblock);
            const selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
            try {
                document.execCommand('copy');
                copyingDone();
            } catch (e) { };
            selection.removeRange(range);
        });

        if (container.classList.contains("highlight")) {
            container.appendChild(copybutton);
        } else if (container.parentNode.firstChild == container) {
            
        } else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
            
            codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
        } else {
            
            codeblock.parentNode.appendChild(copybutton);
        }
    });
</script>
</body>

</html>
